#pragma once 
//----------------------------------------------------------------------------------------------------------------
//			Filename:		IrradianceCache.h
//			  Author:		Adrian De Barro
//			    Date:		13/08/2015
//----------------------------------------------------------------------------------------------------------------
//			  Status: REFACTORING
//----------------------------------------------------------------------------------------------------------------
#include <memory>
#include <string>
#include <iostream>
#include <mutex>
#include <assert.h>


#include <Peer/Peer2Peer-Peer/P2pPeer.h>

#include "..\Integrators\Integrator.h"
#include "..\AccelerationStructures\CachingStructure\Octree.h"
#include "..\Scene\Scene.h"
#include "..\CoreClasses\RGBObject\RGBColor.h"
#include "..\AccelerationStructures\BVH.h"
#include "..\CoreClasses\Ray\Ray.h"
#include "..\CoreClasses\Sampler\Sampler.h"
#include "..\CoreClasses\Sampler\Jittered.h"
#include "..\Tracer\Tracer.h"
#include "..\CollisionDetails\ShadeRec.h"
#include "..\CoreClasses\UtilClasses\Common.h"
#include "..\CoreClasses\Logger\Logger.h"
#include "..\Material\Material.h"
#include "..\Integrators\RadianceDetails\RadiancePoint.h"
#include "..\CoreClasses\GeometricObjects\GeometricObjects.h"
#include "..\CoreClasses\GeometricObjects\Quad.h"
#include "..\CoreClasses\Point\Point3D\Point3D.h"
#include "..\AccelerationStructures/CachingStructure/LockingOctree.h"
#include "../Networking/ObservableEventsMonitor/ICUpdatesMonitor.h"


using std::string;
using namespace Network::Peer;

namespace Engine
{
	#define MAXH_DEPTH 1
	//#define DEBUG_MODE
	#define NUMBER_SAMPLES 529
	#define NUMBER_SETS 83
	#define USE_ORTHAGONAL_SHIFTING true
	#define DEBUG_SAMPLES
	#define AMOUNT_DEVISABLE_CONSTANT 60;
	#define MONITORING_MODE
	#define DEBUG_MODE

	//----------------------------------------------------------------------------------------------------------------
	//			Class Name: IrradianceCache
	//		   Description: The algorithm taking care of the in-direct lighting
	//----------------------------------------------------------------------------------------------------------------
	//			    Status: REFACTORING
	//----------------------------------------------------------------------------------------------------------------
	class IrradianceCache:public Integrator
	{
	public:
		
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		IrradianceCache() : m_currentShadeRec(ShadeRec())
		{
			m_currentShadeRec = ShadeRec();
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: Scene*, Tracer*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		IrradianceCache(Scene* p_scene, Tracer* p_tracerPtr) : m_currentShadeRec(ShadeRec())
		{
			#ifndef MULTI_THREADED
				octreeStructure = new Octree(p_scene);
			#else 
				octreeStructure = new LockingOctree(p_scene);
			#endif

			accelerationStructure = new BVH(p_scene->m_sceneObjects, MIDDLE_POINT, true);
			m_scenePtr = p_scene;
			m_sampler = new Jittered(NUMBER_SAMPLES, NUMBER_SETS);
			m_sampler->GenerateSamples();
			m_sampler->MapSamplesToUnitDisk();
			m_sampler->MapSamplesToHemisphere(10, true);
			m_tracerPtr = p_tracerPtr;
			m_currentShadeRec = ShadeRec();
			GetMinmumRayLength();
			//TODO : Init Peer
			
		}

		///-------------------------------------------------------------------------------------------------
		/// <summary>	Constructor. </summary>
		///
		/// <remarks>	Adrian, 21/09/2015. </remarks>
		///
		/// <param name="p_scene">	  	[in,out] If non-null, the scene. </param>
		/// <param name="p_tracerPtr">	[in,out] If non-null, the tracer pointer. </param>
		/// <param name="p_peerClass">	[in,out] If non-null, the peer class. </param>
		///-------------------------------------------------------------------------------------------------

		IrradianceCache(Scene* p_scene, Tracer* p_tracerPtr, P2pPeer<RadiancePoint, IrradianceCache>* p_peerClass) : m_currentShadeRec(ShadeRec())
		{
			#ifndef MULTI_THREADED
				octreeStructure = new Octree(p_scene);
			#else 
				octreeStructure = new LockingOctree(p_scene);
			#endif

			accelerationStructure = new BVH(p_scene->m_sceneObjects, MIDDLE_POINT, true);
			m_scenePtr = p_scene;
			m_sampler = new Jittered(NUMBER_SAMPLES, NUMBER_SETS);
			m_sampler->GenerateSamples();
			m_sampler->MapSamplesToUnitDisk();
			m_sampler->MapSamplesToHemisphere(10, true);
			m_tracerPtr = p_tracerPtr;
			m_currentShadeRec = ShadeRec();
			GetMinmumRayLength();
			//TODO : Init Peer

		}

		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: CONSTRUCTOR
		//		Return Value: VOID
		//		  Parameters: Scene*, BVH*, Tracer*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		IrradianceCache(Scene* p_scene, Engine::BVH* p_bvh, Tracer* p_tracerPtr) : m_currentShadeRec(ShadeRec())
		{
			#ifndef MULTI_THREADED
				octreeStructure = new Octree(p_scene);
			#else 
				octreeStructure = new LockingOctree(p_scene);
			#endif

			accelerationStructure = p_bvh;
			m_scenePtr = p_scene;
			m_sampler = new Jittered(NUMBER_SAMPLES, NUMBER_SETS);
			m_sampler->GenerateSamples();
			m_sampler->ConvertSamplesToHemisphere();
			m_tracerPtr = p_tracerPtr;
			m_currentShadeRec = ShadeRec();
			GetMinmumRayLength();
			// TODO: Init Peer
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetIndirectLightingAtPoint
		//		Return Value: RGBColor
		//		  Parameters: CollisionDetails
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
	    using Integrator::GetIndirectLightingAtPoint;
		RGBColor GetIndirectLightingAtPoint(ShadeRec& p_intersectionDetails)
		{
			m_currentShadeRec = ShadeRec(p_intersectionDetails);
			Point3D normal(p_intersectionDetails.normal.x, p_intersectionDetails.normal.y, p_intersectionDetails.normal.z);
			RGBColor output = GetOrCreate(normal, p_intersectionDetails);
			return output;
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetOrCreate
		//		Return Value: RGBColor
		//		  Parameters: RadiancePoint*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		RGBColor GetOrCreate(Point3D p_normal, ShadeRec& p_collisionDetails)
		{
			RadiancePoint* radiancePoint = nullptr;
			bool radianceExists = octreeStructure->GetRadiancePoint(p_collisionDetails.localHitPoint, radiancePoint);
			vector<RadiancePoint*> availableRadiancePoints;
			//create new radiane point
			if (radianceExists)
			{
				return octreeStructure->GetRadiancePoint(p_collisionDetails.localHitPoint)->radiance;
			}
			else if (!radianceExists && !octreeStructure->GetPoints(p_collisionDetails, p_normal, availableRadiancePoints))
			{
				if (m_TestFlagCreateSamples == true)
				{
					RGBColor toReturn = FireSamples(p_collisionDetails);
					#ifdef DEBUG_MODE
						return RGBColor(1, 0, 0);
					#else
						return p_currentPoint->radiance;
					#endif
				}
			}
			else if (availableRadiancePoints.size() > 0 )
			{
				return ComputeInterpolation(availableRadiancePoints, p_collisionDetails.localHitPoint, p_normal);
			}	
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: ComputeInterpolation
		//		Return Value: RGBColor
		//		  Parameters: vector<RadiancePoint*>, RadiancePoint*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		RGBColor ComputeInterpolation(vector<RadiancePoint*>& points, Point3D p_position, Point3D p_normal)
		{
			float output = 0;
			RGBColor summationIrradianceWeight = 0;
			float summationWeight = 0;
			  
			for (int index = 0; index < points.size(); index++)
			{
				RadiancePoint* currentPoint = points[index];

				float currentWeight = currentPoint->GetWeighting(p_position, p_normal);

				summationIrradianceWeight = summationIrradianceWeight + currentPoint->radiance*currentWeight;
				summationWeight += currentWeight;
			}

			return summationIrradianceWeight / summationWeight;
		}

		void NewFrame()
		{
			
		}


		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: FireSamples
		//		Return Value: RGBColor
		//		  Parameters: ShadeRec
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		RGBColor FireSamples(ShadeRec& sr)
		{
			RadiancePoint* newRadiancePoint = new RadiancePoint();
			newRadiancePoint->m_position = sr.localHitPoint;
			newRadiancePoint->hitPointNormal = Point3D(sr.normal.x, sr.normal.y, sr.normal.z);
			return FireSamplesNAdd(newRadiancePoint);
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: FireSamples
		//		Return Value: RGBColor
		//		  Parameters: RadiancePoint*
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		RGBColor FireSamplesNAdd(RadiancePoint* p_currentPoint)
		{
			ShadeRec rec;
			int depth = 1;
			vector<ShadeRec*> tester;

			Sample(p_currentPoint, depth);

			if (p_currentPoint->radiance != 0)
			{
				octreeStructure->InsertPoint(p_currentPoint);
				AddToUpdates(p_currentPoint);
			}

			return p_currentPoint->radiance;
		}
		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: Sample
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: 
		//					  TODO fix after shibanek...TO REFACTOR
		//----------------------------------------------------------------------------------------------------------------
		RGBColor Sample(RadiancePoint* p_collisionPoint, int currentDepth)
		{
			vector<ShadeRec*> collisions;
			vector<Point3D> SamplePositions;
			vector<Point3D> samples;

			if (currentDepth == 1)
			{
				RGBColor resultingOutput;
				for (int sampleIndex = 0; sampleIndex < NUMBER_SAMPLES; ++sampleIndex)
				{
					try
					{
						Point3D currentSample = m_sampler->SampleHemispherical(sampleIndex, p_collisionPoint->hitPointNormal, p_collisionPoint->m_collisionRay.d);
						currentSample.normalize();
						samples.push_back(currentSample);
						//Ray currentRay(p_collisionPoint->GetEpsillonHitPoint(), p_collisionPoint->hitPointNormal);
					
						Ray currentRay(p_collisionPoint->GetEpsillonHitPoint(), currentSample);
						ShadeRec* currentCollision = new ShadeRec();
						FireSamplesAtPoint(currentRay, currentCollision);
						//currentCollision->object->PrintObjectName();
						collisions.push_back(currentCollision);
					}
					catch (exception e)
					{
						std::string excep = e.what();
						Logger::LogError(excep+ "During sampling");
					}
				}

				p_collisionPoint->ComputeRadianceNHarmonivValue(collisions, m_minimumRayLength);
				std::for_each(collisions.begin(), collisions.end(), std::default_delete<ShadeRec>());
				return p_collisionPoint->radiance;
			}
			else
			{
				int amountSamples = m_sampler->GetNumSamples();
				
				RGBColor resultingOutput;

				int nextDepth = --currentDepth;

				for (int sampleIndex = 0; sampleIndex < NUMBER_SAMPLES; ++sampleIndex)
				{ 
					Point3D direction = m_sampler->SampleHemispherical(sampleIndex);
					Ray currentRay(p_collisionPoint->GetEpsillonHitPoint(), direction);
					ShadeRec rec;
					double tmin = INT_MAX;
					Point3D intersectionPoint = accelerationStructure->TestIntersection(currentRay, rec, tmin);

					RadiancePoint * currentPoint = new RadiancePoint(intersectionPoint);
					resultingOutput += Sample(currentPoint, nextDepth);
					octreeStructure->InsertPoint(currentPoint);
					AddToUpdates(currentPoint);
				}
				p_collisionPoint->radiance = resultingOutput / NUMBER_SAMPLES;

				octreeStructure->InsertPoint(p_collisionPoint);

				return resultingOutput;
			}
		}

		///-------------------------------------------------------------------------------------------------
		/// <summary>	Adds to the updates. </summary>
		///
		/// <remarks>	Adrian, 25/09/2015. </remarks>
		///
		/// <param name="p_radiancePoint">	[in,out] If non-null, the radiance point. </param>
		///-------------------------------------------------------------------------------------------------

		void AddToUpdates(RadiancePoint* p_radiancePoint)
		{
			mtx.lock();
			m_localUpdates.push_back(p_radiancePoint);
			if (p_radiancePoint->GotFrom() == 0)
				IncrementLocalRadiancePointCount();
			else
				IncrementNetworkRadiancePointCount();
			mtx.unlock();
		}

		///-------------------------------------------------------------------------------------------------
		/// <summary>	Increment local radiance point count. </summary>
		///
		/// <remarks>	Adrian, 19/10/2015. </remarks>
		///-------------------------------------------------------------------------------------------------

		void IncrementLocalRadiancePointCount()
		{
			m_localRadianceCount++;
		}

		///-------------------------------------------------------------------------------------------------
		/// <summary>	Increment network radiance point count. </summary>
		///
		/// <remarks>	Adrian, 19/10/2015. </remarks>
		///-------------------------------------------------------------------------------------------------

		void IncrementNetworkRadiancePointCount()
		{
			m_networkRadianceCount++;
		}

		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: FireSamplesAtPoint
		//		Return Value: VOID
		//		  Parameters: Ray, CollisionDetails
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void FireSamplesAtPoint(Ray currentRay, ShadeRec* details)
		{
			double tmin = INT_MAX;
			currentRay.d.normalize();
			bool intersectionTest = accelerationStructure->TestIntersection(currentRay, *details, tmin);
			if (intersectionTest)
			{
				Material* currentMaterial = nullptr;
				if (details->m_hitTriangle != nullptr)
					currentMaterial = details->m_hitTriangle->GetMaterial();
				else
					currentMaterial = details->object->GetMaterial();

				RGBColor colorAtPoint;
				details->myColor = currentMaterial->Shade(*details, m_scenePtr->m_sceneLight,m_tracerPtr, colorAtPoint);
			}
			else
			{
				details->myColor = RGBColor(0);
			}
		}

		bool CheckForUpdates()
		{
			return m_localUpdates.size() > 0;
		}

		vector<RadiancePoint*> GetLocalUpdates()
		{
			mtx.lock();
			vector<RadiancePoint*> temp = m_localUpdates;
			EmptyLocalUpdates();
			mtx.unlock();
			
			return temp;
		}

		void EmptyLocalUpdates()
		{
			m_localUpdates.clear();
			#ifdef _DEBUG
				assert(m_localUpdates.size() == 0);
			#endif
		}

		#ifndef MULTI_THREADED
			Octree* GetOctree()
			{
				return octreeStructure;
			}
		#else 
			LockingOctree* GetOctree()
			{
				return octreeStructure;
			}
		#endif
		

		int GetLocalRadianceCount()
		{
			return m_localRadianceCount;
		}

		int GetNetworkRadianceCount()
		{
			return m_networkRadianceCount;
		}

		void DontCreateSamples()
		{
			m_TestFlagCreateSamples = false;
		}

		void CreateSamples()
		{
			m_TestFlagCreateSamples = true;
		}

		~IrradianceCache()
		{}

	private:
		/// <summary>	The caching structure. </summary>
		#ifndef MULTI_THREADED
			Octree* octreeStructure;
		#else 
			LockingOctree *octreeStructure;
		#endif
			/// <summary>	The scene. </summary>
		Scene* m_scenePtr;
		/// <summary>	The acceleration structure. </summary>
		BVH* accelerationStructure;
		/// <summary>	The sampler </summary>
		Sampler* m_sampler;
		/// <summary>	The tracer. </summary>
		Tracer* m_tracerPtr;
		/// <summary>	The accepted minimum ray length</summary>
		double m_minimumRayLength;
		/// <summary>	The current shade record. </summary>
		ShadeRec& m_currentShadeRec;
		/// <summary>	The local updates. </summary>
		vector<RadiancePoint*> m_localUpdates;

		std::mutex mtx;
		/// <summary>	true to test flag dont create. </summary>
		bool m_TestFlagCreateSamples = true;

		int m_localRadianceCount;
		int m_networkRadianceCount;



		//----------------------------------------------------------------------------------------------------------------
		//		 Method Name: GetMinimumRayLength
		//		Return Value: VOID
		//		  Parameters: VOID
		//			  Status: READY
		//----------------------------------------------------------------------------------------------------------------
		void GetMinmumRayLength()
		{
			double rayLength = accelerationStructure->rootNode->boundingBox->GetLongestLengthAxis();
			m_minimumRayLength = rayLength / AMOUNT_DEVISABLE_CONSTANT;
		}
	protected:
	};
	//----------------------------------------------------------------------------------------------------------------

}
//----------------------------------------------------------------------------------------------------------------